#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
The TUI(Text-based User Interface) module.
"""

# Author: Likayo Lee

__all__ = {"main"}

import curses
import locale
import os
import sys

from wb_parser import parse as wb_parse
from execute import execute as wb_execute
from symbol_table import symbol_table as wb_symbol_table

from inputpad import InputPad
from maintext import MainText

def init_tui(stdscr, encoding):
    """
    Initializes all parameters and UI elements.

    Args:
        encoding: the enconding of user terminal.

    Returns:
        The main text pad, command input pad and debug window.
    """
    # A trick to avoid maximum recursion depth exceeded caused by 
    #   curses.textpad module
    sys.setrecursionlimit(10000)

    # A trick to reduce the delay for escape key.
    #   for more details, see http://en.chys.info/2009/09/esdelay-ncurses/
    os.environ["ESCDELAY"] = "25"

    # test text
    # with open("test/test_chs_small.txt") as fd:
    #     s = fd.read().decode('utf8')
    s = u"Welcome to Linux Weibo!"

    curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
    stdscr.bkgd(' ', curses.color_pair(1))      # set color

    # initialize main text pad & debug pad
    win_height, win_width = stdscr.getmaxyx()
    pad_height, pad_width = win_height-2, win_width
    textpad = MainText(pad_height, pad_width)
    textpad.pad.bkgd(' ', curses.color_pair(1)) # set color
    textpad.replace_content(s, "plain", "", encoding)
    debugwin = curses.newwin(1, win_width, win_height-2, 0)
    debugwin.bkgd(' ', curses.color_pair(1))    # set color
    # debugwin.addstr(0, 0, "lines = %d" % textpad.lines)

    # initialize command input pad
    stdscr.addch(win_height-1, 0, ":")
    cmdpad = InputPad(1, win_width-1, win_height-1, 1,
                        multiline=False,history=True,debug_win=debugwin)
    cmdpad.win.bkgd(' ', curses.color_pair(1))  # set color
    return textpad, cmdpad, debugwin


def inside_view_mode(stdscr, textpad, debugwin):
    """
    Interacts with user input in view mode.

    Deals with arrow keys, resizing event, shortcut keys, etc.
    Only returns when exiting the view mode.

    Returns:
        The next action that should be taken. Possible values are:

        {"action": "reexec_cmd", "cmd": cmd}: re-execute the command that 
                generates the current content of main text.
        {"action": "input_cmd"}:  input weibo commands.
    """
    next_action = {"action": "noop"}
    while True:
        c = stdscr.getch()
        if c == curses.KEY_RESIZE:
            win_height, win_width = stdscr.getmaxyx()
            pad_height, pad_width = win_height-2, win_width
            textpad.resize(pad_height, pad_width)
        elif c == curses.KEY_UP:
            textpad.current_pos -= 1
        elif c == curses.KEY_DOWN:
            textpad.current_pos += 1
        elif c == curses.KEY_LEFT:
            textpad.current_pos -= 10
        elif c == curses.KEY_RIGHT:
            textpad.current_pos += 10
        elif c == ord('r'):                 # refresh
            next_action = {"action": "reexec_cmd", "cmd": textpad.refresh_cmd}
            break
        elif c == ord(':'):
            next_action = {"action": "input_cmd"}
            break
        refresh_view_mode(stdscr, textpad, debugwin)
    return next_action


def refresh_view_mode(stdscr, textpad, debugwin):
    """
    Refreshes the screen in view mode.
    """
    if textpad.current_pos < 0:
        textpad.current_pos = 0
    if textpad.current_pos > textpad.lines - 1:
        textpad.current_pos = textpad.lines - 1
    # debugwin.erase()
    # debugwin.addstr(0, 0, "lines = %d, current_pos = %d" % (textpad.lines, textpad.current_pos))
    stdscr.refresh()
    textpad.refresh()
    debugwin.refresh()


def inside_cmd_mode(stdscr, cmdpad, debugwin):
    """
    Interacts with user input in command mode.

    Deals with arrow keys, resizing event, shortcut keys, escape key.
    Only returns when exiting the command mode.

    Returns:
        The next action that should be taken. Possible values are:

        {"action": "exec_cmd", "cmd": ___}:  go to exec mode and exec command
        # {"action": "edit_wb", "cmd": ___}:  go to edit mode to input a weibo.
        {"action": "cmd_abort"}:  go back to the view mode.
    """
    next_action = {"action": "noop"}
    debugwin.erase()
    debugwin.refresh()
    while True:
        cmd = cmdpad.edit()
        if isinstance(cmd, str):
            debugwin.erase()
            debugwin.addstr(0, 0, "Command: %s" % repr(cmd))
            # if cmd.startswith("send wb"):
            #     next_action = {"action": "edit_wb", "cmd": cmd}
            #     break
            next_action = {"action": "exec_cmd", "cmd": cmd}
            break
        else:   # input abort (esc key pressed)
            debugwin.erase()
            debugwin.addstr(0, 0, "Command Aborted.")
            next_action = {"action": "cmd_abort"}
            break
        debugwin.refresh()
    cmdpad.win.erase()
    cmdpad.win.refresh()
    return next_action


def inside_edit_mode(stdscr, inputpad, debugwin, cmd):
    """
    Interacts with user input in edit mode.

    Deals with arrow keys, resizing event, shortcut keys, escape key.
    Only returns when exiting the edit mode.

    Args:
        cmd: the command that edit mode is editing for.

    Returns:
        The next action that should be taken. Possible values are:

        {"action": "edit_complete", "cmd": ___, "content": ___}: input succeeded. 
        {"action": "edit_abort", "cmd": ___}: input failed.
    """
    next_action = {"action": "noop"}
    debugwin.erase()
    debugwin.refresh()
    while True:
        text = inputpad.edit()
        if isinstance(text, str):
            debugwin.erase()
            # debugwin.addstr(0, 0, "Command: %s" % repr(text))
            debugwin.addstr(0, 0, "Edit Succeeds.")
            # c = stdscr.getch()
            next_action = {"action": "edit_complete", "cmd": cmd, "content": text}
            break
        else:   # input abort (esc key pressed)
            debugwin.erase()
            debugwin.addstr(0, 0, "Edit Aborted.")
            # debugwin.refresh()
            next_action = {"action": "edit_abort", "cmd": cmd}
            break
        debugwin.refresh()
    return next_action


def inside_exec_mode(stdscr, debugwin, client, cmd, content):
    """
    Interacts with user input in exec mode.

    Parse and exec the command.

    Args:
        cmd: the command to be executed
        content: the message to be sent. Useful for send_wb and send_comment.

    Returns:
        The next action that should be taken. Possible values are:

        {"action": "exec_failed"}: go back to view mode without changing 
                viewing content.
        {"action": "view_new_content", "content": ___, "content_type": ___, 
                "cmd":___}: go to view mode and change viewing content.
        {"action": "edit_msg", "cmd": cmd}: go to edit mode to edit a message
                (wb or comment).
    """
    next_action = {"action": "noop"}
    cmds_need_content = {"send_wb", "send_comment"}
    parse_result = wb_parse(cmd)            # See wb_parser.py for details.
    if parse_result["cmd"] == "exception":  # Parse failed.
        debugwin.erase()
        debugwin.addstr(0,0, "Error: %s" % parse_result["error_message"])
        next_action = {"action": "exec_failed"}
    elif parse_result["cmd"] in cmds_need_content and content == None:
        next_action = {"action": "edit_msg", "cmd": cmd}
    else:
        if parse_result["cmd"] in cmds_need_content:
            parse_result["content"] = content
        exec_result = wb_execute(client, parse_result)  # See execute.py for details.
        if not exec_result["state"]:        # Exec failed.
            debugwin.erase()
            debugwin.addstr(0,0, "Error: %s" % repr(exec_result["warning_message"]))
            next_action = {"action": "exec_failed"}
        else:
            debugwin.erase()
            debugwin.addstr(0,0, "Command Success: %s" % repr(cmd))
            next_action = {"action": "view_new_content",
                            "content": exec_result["result"],
                            "content_type": exec_result["return_type"],
                            "cmd": cmd}
    return next_action


def main(stdscr, encoding, client):
    # the following simulate a deterministic state machine.
    textpad, cmdpad, debugwin = init_tui(stdscr, encoding)
    modes = {"view_mode", "cmd_mode", "edit_mode", "exec_mode"}
    current_mode = "view_mode"
    refresh_view_mode(stdscr, textpad, debugwin)
    next_action = {"action": "noop"}

    while True:
        assert current_mode in modes
        last_action = next_action
        next_action = None

        if current_mode == "view_mode":
            if (last_action["action"] == "view_new_content" 
                and last_action["content_type"] != "no_type"):
                # enumerate the content array
                if last_action["content_type"] in ("weibo", "comment"):
                    numbered_content = dict()
                    i = 1
                    for cont in last_action["content"]:
                        numbered_content[i] = cont
                        i += 1
                    last_action["content"] = numbered_content
                # set symbol table
                if last_action["content_type"] == "weibo":
                    wb_symbol_table.clear_weibos()
                    for i, wb in last_action["content"].iteritems():
                        wb_symbol_table.weibos[i] = int(wb["id"])
                elif last_action["content_type"] == "comment":
                    wb_symbol_table.clear_comments()
                    for i, comment in last_action["content"].iteritems():
                        wb_symbol_table.comments[i] = int(comment["id"])
                textpad.replace_content(last_action["content"], 
                                        last_action["content_type"],
                                        last_action["cmd"], # command needed for refresh
                                        encoding)
            textpad.touchwin()
            refresh_view_mode(stdscr, textpad, debugwin)
            next_action = inside_view_mode(stdscr, textpad, debugwin)

        elif current_mode == "cmd_mode":
            next_action = inside_cmd_mode(stdscr, cmdpad, debugwin)

        elif current_mode == "edit_mode":
            # entry: initialize weibo input pad (leftmost)
            win_height, win_width = stdscr.getmaxyx()
            stdscr.addstr(0,0,"==== Please input: ====")
            stdscr.touchwin()
            stdscr.refresh()
            inputpad = InputPad(win_height-5, win_width, 1, 0, 
                                multiline=True,debug_win=debugwin)
            next_action = inside_edit_mode(stdscr, inputpad, debugwin, 
                                            cmd=last_action["cmd"])
            del inputpad

        else:   # current_mode is exec mode
            # {"action": "exec_cmd", "cmd": cmd}
            cmd = last_action["cmd"]
            content = last_action["content"] if "content" in last_action else None
            next_action = inside_exec_mode(stdscr, debugwin, client, cmd, content)


        assert next_action["action"] in {
                                        "input_cmd", "reexec_cmd",
                                        "exec_cmd", "cmd_abort",# "edit_wb",
                                        "edit_complete", "edit_abort",
                                        "exec_failed", "edit_msg", "view_new_content",
                                        }
        # == From view mode ==
        if next_action["action"] == "input_cmd":
            current_mode = "cmd_mode"
        elif next_action["action"] == "reexec_cmd":
            current_mode = "exec_mode"

        # == From cmd mode ==
        elif next_action["action"] == "exec_cmd":
            current_mode = "exec_mode"
        elif next_action["action"] == "cmd_abort":
            current_mode = "view_mode"
        # elif next_action["action"] == "edit_wb":
        #     current_mode = "edit_mode"

        # == From edit mode ==
        elif next_action["action"] == "edit_complete":
            current_mode = "exec_mode"
        elif next_action["action"] == "edit_abort":
            current_mode = "view_mode"

        # == From exec mode ==
        elif next_action["action"] == "exec_failed":
            current_mode = "view_mode"
        elif next_action["action"] == "view_new_content":
            current_mode = "view_mode"
        elif next_action["action"] == "edit_msg":
            current_mode = "edit_mode"
        else:
            assert False


if __name__ == '__main__':
    locale.setlocale(locale.LC_ALL, '')
    code = locale.getpreferredencoding()
    curses.wrapper(main, code, client=None)
